package com.niodata.dp.expression;

import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Feature;
import com.googlecode.aviator.Options;
import com.googlecode.aviator.runtime.type.AviatorFunction;
import com.niodata.dp.util.DateParser;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.ImplementingClassMatchProcessor;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;

public class DpExpressionEvaluator {

  public static final String EXPRESSION_PREFIX = "$Expression(";
  public static final String INTERNAL_VAR_CURRENT_DATETIME = "CURRENT_DATETIME";
  private static Logger logger = Logger.getLogger(DpExpressionEvaluator.class);

  static {
    //启用编译缓存
    AviatorEvaluatorInstance evaluator = AviatorEvaluator.getInstance();
    evaluator.setCachedExpressionByDefault(true);
    //设置LRU缓存并设置大小
    evaluator.useLRUExpressionCache(1000);
    //禁用一些不必要且影响系统安全的特性
    //evaluator.disableFeature(Feature.Module);
    evaluator.disableFeature(Feature.NewInstance);
    evaluator.disableFeature(Feature.Use);
    evaluator.disableFeature(Feature.StaticFields);
    evaluator.disableFeature(Feature.StaticMethods);

    //设置最大循环次数
    evaluator.setOption(Options.MAX_LOOP_COUNT, 5000);

    FastClasspathScanner scanner = new FastClasspathScanner("com.niodata.dp.expression");
    scanner.matchClassesImplementing(AviatorFunction.class,
          new ImplementingClassMatchProcessor<AviatorFunction>() {
            @Override
            public void processMatch(Class<? extends AviatorFunction> implementingClass) {

            }
          });
    ScanResult result = scanner.scan();
    List<String> classes = result.getNamesOfAllClasses();
    try {
      int functionCount = 0;
      for (String clazz : classes) {
        Class clss = Class.forName(clazz);
        if (clazz.startsWith("com.niodata.dp.")
              && !Modifier.isAbstract(clss.getModifiers())
              && AviatorFunction.class.isAssignableFrom(clss)) {
          evaluator.addFunction((AviatorFunction) clss.newInstance());
          functionCount++;
        }
      }
      logger.info("add dp script function " + functionCount);
    } catch (Exception ex) {
      logger.error(ex);
    }
  }

  public static Object execute(String expression) {
    return execute(expression, Collections.emptyMap());
  }

  /**
   * exec expression.
   *
   * @param expression expression
   * @param env env
   * @return value
   */
  public static Object execute(String expression, Map<String, Object> env) {
    if (env != null && !env.isEmpty()) {
      for (String key : env.keySet()) {
        Object v = env.get(key);
        if (v != null && v instanceof String
              && DateParser.containDateTimeExpression(v.toString())) {
          v = DateParser.replaceDateStartValue(v.toString(), new Date());
          env.put(key, v);
        }
      }
    }
    String myExpression = expression;
    if (myExpression.startsWith(EXPRESSION_PREFIX)) {
      myExpression = myExpression.substring(EXPRESSION_PREFIX.length(), myExpression.length() - 1);

    }
    Map<String, Object> myEnv = new HashMap<String, Object>(env != null ? env.size() + 1 : 10);
    if (!myEnv.containsKey(INTERNAL_VAR_CURRENT_DATETIME)) {
      myEnv.put(INTERNAL_VAR_CURRENT_DATETIME, new Date());
    }
    if (env != null && env.size() > 0) {
      myEnv.putAll(env);
    }
    return AviatorEvaluator.execute(myExpression, myEnv, true);
  }

  public static Map<String, Object> newEnv(final Object... args) {
    return AviatorEvaluator.newEnv(args);
  }

  public static void registerFunction(Class<? extends AviatorFunction> clazz) throws Exception {
    AviatorFunction function = clazz.newInstance();
    AviatorEvaluator.getInstance().addFunction(function);
  }


}
